%!Tex Program = xelatex
%\documentclass[a4paper]{article}
\documentclass[a4paper]{ctexart}
\usepackage{xltxtra}
\usepackage{listings}
\usepackage{fontspec}
\usepackage{hyperref}

% 自定义字体
% 如果使用 ctex 字体，注释掉以下部分.
\setCJKmainfont[
BoldFont={WenQuanYi Zen Hei:style=Regular},
ItalicFont={AR PL KaitiM GB:style=Regular}]
{AR PL UMing CN:style=Light}
\setCJKsansfont{WenQuanYi Zen Hei Sharp:style=Regular}
\setCJKmonofont{WenQuanYi Zen Hei Mono:style=Regular}

%\setmainfont[Mapping=tex-text]{AR PL UMing CN:style=Light}
%\setmainfont[Mapping=tex-text]{AR PL UKai CN:style=Book}
%\setmainfont[Mapping=tex-text]{WenQuanYi Zen Hei:style=Regular}
%\setmainfont[Mapping=tex-text]{WenQuanYi Zen Hei Sharp:style=Regular}
%\setmainfont[Mapping=tex-text]{AR PL KaitiM GB:style=Regular} 
%\setmainfont[Mapping=tex-text]{AR PL SungtiL GB:style=Regular} 
%\setmainfont[Mapping=tex-text]{WenQuanYi Zen Hei Mono:style=Regular} 

\lstdefinestyle{shell}{
    language=sh,
    basicstyle=\ttfamily,
    keywordstyle=\color{blue},
    commentstyle=\color{gray},
    backgroundcolor=\color{lightgray}
}


\title{第六讲：cmake 和科学计算软件包 deal.II}
\author{王何宇}
\date{}

\begin{document}
\maketitle
\pagestyle{empty}

\section{cmake}

CMake是一个跨平台的开源构建工具，用于管理软件项目的构建过程。它能够自
动生成与编译系统无关的构建脚本(Makefile)，使得开发者能够以简单和统一的
方式构建、测试和打包他们的软件。

CMake的主要目标是简化软件项目的构建过程，并提供可移植性。相比于其他构
建工具（如make），CMake采用了一种更高级的构建脚本语言，即
CMakeLists.txt文件。通过这个文件，开发者可以描述项目的源代码、依赖关系、
编译选项和安装规则等信息。

CMake支持多种编译器和操作系统，包括Windows、Linux、macOS等。它能够根据
用户的配置生成不同平台上的本地构建文件（如Makefile或Visual Studio解决
  方案）来进行构建。这意味着开发者可以使用相同的CMake脚本构建项目，而
不必为每个平台编写独立的构建脚本。

使用CMake，开发者可以灵活地组织项目结构，管理库和可执行文件的构建过程，
处理外部依赖，自定义构建选项，并且能够轻松地集成到现有的开发工作流程中。
它还支持各种编程语言，如C++、C、Python等，使得开发者能够在不同的项目中
使用统一的构建工具。

总而言之，CMake是一个强大的跨平台构建工具，它简化了软件项目的构建过程，
提高了开发效率，并促进了代码的可移植性和可维护性。无论是小型项目还是大
型项目，CMake都是一个值得推荐的构建工具。

还是以我们的 hello 项目为例（hello.cmake），首先我们在项目目录下构建一个叫
CMakeLists.txt 的文件，内容为：

\begin{verbatim}
cmake_minimum_required(VERSION 3.5)     # 指定CMake的最低版本要求

project(Hello)                          # 指定项目的名称

set(SOURCES main.c src/hello.c)         # 定义源文件列表

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
                                        # 添加头文件搜索路径

add_executable(hello ${SOURCES})        # 声明可执行文件及其所需的源文件
\end{verbatim}

这里 \verb|${CMAKE_CURRENT_SOURCE_DIR}| 称为 cmake 的预定义变量，
表示当前源码目录的路径。

cmake 提供了较为完整的逻辑结构，使我们可以根据不同的情况，构建出通用的编译和运行过程。
比如，将我们的 hello 项目进一步做如下优化：

\begin{verbatim}
cmake_minimum_required(VERSION 3.5)

project(Hello)

set(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/main.c)

# 设置 rpath 参数
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")

if(APPLE)
    set(CMAKE_INSTALL_RPATH "@executable_path/../lib")
elseif(UNIX)
    set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib")
endif()

add_library(hello_lib SHARED ${CMAKE_CURRENT_SOURCE_DIR}/src/hello.c)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib)

add_executable(hello ${SOURCES})
target_link_directories(hello PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/lib)
target_link_libraries(hello hello_lib)

install(TARGETS hello DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
install(TARGETS hello_lib DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ 
DESTINATION ${CMAKE_INSTALL_PREFIX}/include)
\end{verbatim}

这里全大写的都是 CMake 的关键字，比如 \verb|PUBLIC|
表示将后面的动态链接库开放给全部源码。然后一行写不下可以换行。
注意最后三行规范了如何发布程序。当我们在 build 目录下运行：
\begin{verbatim}
cmake -DCMAKE_INSTALL_PREFIX=/home/hywang/temp/hello ..
\end{verbatim}
然后再 \verb|make| ，再 \verb|make install|，
我们的 hello 项目就会安装到指定目录中去。CMake 还有一个优点是，
源码目录始终可以保持干净。最后我们只要把 build 目录删除就行了。所以 CMake
越来越受到大家欢迎。

以下是 CMake 中常用的预定义变量：

\begin{itemize}
  \item \texttt{CMAKE\_SOURCE\_DIR}：CMake 项目的根目录路径。
  \item \texttt{CMAKE\_BINARY\_DIR}：构建目录的路径，即生成的 Makefile
    和二进制文件的存放位置。
  \item \texttt{CMAKE\_CURRENT\_SOURCE\_DIR}：当前处理的
    \texttt{CMakeLists.txt} 文件所在的目录路径。
  \item \texttt{CMAKE\_CURRENT\_BINARY\_DIR}：与
    \texttt{CMAKE\_CURRENT\_SOURCE\_DIR} 对应的构建目录路径。
  \item \texttt{CMAKE\_INSTALL\_PREFIX}：安装目录的路径，默认为
    \texttt{/usr/local}（或根据的安装设置而定）。
  \item \texttt{CMAKE\_MODULE\_PATH}：指定额外的模块路径，用于查找
    CMake 模块文件。
  \item \texttt{CMAKE\_BUILD\_TYPE}：指定构建类型，例如 \texttt{Debug}、
    \texttt{Release}、\texttt{RelWithDebInfo} 等。
  \item \texttt{PROJECT\_NAME}：通过 \texttt{project()} 命令设置的项目
    名称。
  \item \texttt{CMAKE\_C\_COMPILER}、\texttt{CMAKE\_CXX\_COMPILER}：C
    和 C++ 编译器的路径。
\end{itemize}

除了这些常用的预定义变量外，CMake 还提供了许多其他预定义变量，用于获取
构建系统和环境的相关信息。可以通过 \texttt{\$\{VARIABLE\_NAME\}} 的方
式在 \texttt{CMakeLists.txt} 文件中使用这些变量。

可以通过 \texttt{cmake --help-variable-list} 命令查看完整的预定义变量
列表，并查阅 CMake 的官方文档以获取更多详细信息。

以下是 CMake 中常用的关键字的 LaTeX 格式输出：

\begin{itemize}
  \item \texttt{project}：定义项目名称和支持的语言。
  \item \texttt{cmake\_minimum\_required}：指定所需的最低版本的 CMake。
  \item \texttt{add\_executable}：将源文件添加到可执行目标。
  \item \texttt{add\_library}：将源文件添加到库目标。
  \item \texttt{target\_link\_libraries}：将库目标与其他库链接。
  \item \texttt{find\_package}：查找并加载外部依赖包。
  \item \texttt{include\_directories}：添加头文件目录。
  \item \texttt{link\_directories}：添加库文件目录。
  \item \texttt{set}：设置变量的值。
  \item \texttt{if}、\texttt{else}、\texttt{elseif}、\texttt{endif}：
    条件语句块。
  \item \texttt{foreach}、\texttt{while}、\texttt{break}、
    \texttt{continue}：循环控制语句。
  \item \texttt{function}、\texttt{return}：定义和调用函数。
  \item \texttt{message}：在构建过程中打印消息。
  \item \texttt{install}：安装目标和文件。
  \item \texttt{configure\_file}：生成配置文件。
\end{itemize}

这些关键字是在编写 \texttt{CMakeLists.txt} 文件时经常使用的，用于定义
项目的结构、编译选项、依赖管理等。根据具体的需求使用这些关键字来构建和
配置 CMake 项目。

请注意，关键字需要按照特定的语法规则使用，并且大小写敏感，因此在使用时
需要注意正确的拼写和格式。

以下是关于学习 CMake 的资料推荐的 LaTeX 格式输出：

\begin{enumerate}
  \item 官方文档：CMake 官方网站提供了详尽的文档，包括教程、命令参考和
    模块文档等。可以访问官方网站获取最新版本的文档：
    \href{https://cmake.org/documentation/}{https://cmake.org/documentation/}
    。
  \item CMake 学习笔记：这是一个由王强维护的中文 CMake 学习笔记，包含了 CMake 的基础知识、常用命令和实际应用案例等内容。您可以通过访问以下链接阅读学习笔记：\href{https://www.hahack.com/codes/cmake/}{https://www.hahack.com/codes/cmake/}。
        
  \item Awesome CMake：这是一个 GitHub 上的精选资源列表，包含了与
    CMake 相关的工具、示例项目、库和文章。可以通过访问以下链接获取更多
    信息：
    \href{https://github.com/onqtam/awesome-cmake}{https://github.com/onqtam/awesome-cmake}
    。
  
\end{enumerate}


\section{deal.II 介绍}
deal.II是一种用于解决偏微分方程的开源C++库。它提供了一个强大而灵活的框
架，用于构建和求解各种复杂的数值模型。deal.II具有高度模块化的设计，使
用户能够根据自己的需求使用库中的不同功能。

deal.II支持二维和三维的有限元方法，可以处理各种类型的偏微分方程，包括
椭圆、抛物线和双曲型方程。它提供了广泛的有限元工具，包括各种常用的有限
元空间、积分公式和网格处理算法。deal.II还提供了丰富的线性代数和求解器
接口，以及用于后处理和可视化的工具。

deal.II的设计理念是提供高效、可扩展和可靠的数值解算。它通过使用现代C++
编程技术和优化算法来实现高性能，同时保持代码的清晰和易读性。deal.II还
注重并行计算的支持，在多核和分布式计算环境中能够充分发挥性能优势。

deal.II是一个活跃的开源项目，得到了全球范围内的数值分析和科学计算社区
的广泛认可和使用。它被广泛应用于各种科学和工程领域，包括材料科学、流体
力学、固体力学、地球科学等。deal.II的文档详尽，并提供丰富的示例和教程，
方便用户学习和使用这个强大的数值库。


\subsection{deal.II 的安装和使用}

deal.II 由于比较复杂，所以它的安装和使用全面采用 cmake。
我们在课程开始让大家安装了源里的 deal.II，
但是如果大家要在工作站级别以上的计算设备中使用 deal.II，充分发挥其效能，
那么定制编译几乎是无法避免的。但这个时候你就会发现一个成体系的计算系统，
必须具备一些基础的计算机知识和技能才能维护起来。
我们下载一个最新版的 deal.II 尝试一下编译。

注意它是 CMake，所以实际上缺省的安装就变得非常简单。
\begin{verbatim}
mkdir build
cd build
cmake ..
\end{verbatim}
注意最后我们得到一张报告：
\begin{verbatim}
#  Configured Features (DEAL_II_ALLOW_BUNDLED = ON, DEAL_II_ALLOW_AUTODETECTION = ON):
#      ( DEAL_II_WITH_64BIT_INDICES = OFF )
#      ( DEAL_II_WITH_ADOLC = OFF )
#      ( DEAL_II_WITH_ARBORX = OFF )
#        DEAL_II_WITH_ARPACK set up with external dependencies
#        DEAL_II_WITH_ASSIMP set up with external dependencies
#        DEAL_II_WITH_BOOST set up with external dependencies
#      ( DEAL_II_WITH_CGAL = OFF )
#        DEAL_II_WITH_COMPLEX_VALUES = ON
#      ( DEAL_II_WITH_CUDA = OFF )
#      ( DEAL_II_WITH_GINKGO = OFF )
#        DEAL_II_WITH_GMSH set up with external dependencies
#        DEAL_II_WITH_GSL set up with external dependencies
#      ( DEAL_II_WITH_HDF5 = OFF )
#      ( DEAL_II_WITH_KOKKOS = OFF )
#        DEAL_II_WITH_LAPACK set up with external dependencies
#      ( DEAL_II_WITH_METIS = OFF )
#      ( DEAL_II_WITH_MPI = OFF )
#        DEAL_II_WITH_MUPARSER set up with external dependencies
#        DEAL_II_WITH_OPENCASCADE set up with external dependencies
#      ( DEAL_II_WITH_P4EST = OFF )
#      ( DEAL_II_WITH_PETSC = OFF )
#      ( DEAL_II_WITH_SCALAPACK = OFF )
#      ( DEAL_II_WITH_SLEPC = OFF )
#      ( DEAL_II_WITH_SUNDIALS = OFF )
#      ( DEAL_II_WITH_SYMENGINE = OFF )
#        DEAL_II_WITH_TBB set up with external dependencies
#      ( DEAL_II_WITH_TRILINOS = OFF )
#        DEAL_II_WITH_UMFPACK set up with external dependencies
#        DEAL_II_WITH_ZLIB set up with external dependencies
\end{verbatim}
我们可以得到一些依赖包的状态：\verb|off| 意味着没有找到，所以编译之后的 deal.II
也会缺少某些功能。比如这里 \verb|DEAL_II_WITH_CUDA = OFF|，
所以最终的库将不支持基于 GPU 的计算加速。有些包写着 
\begin{verbatim}
#        DEAL_II_WITH_GSL set up with external dependencies
\end{verbatim}
那就意味着找到一个可以使用版本，最终程序将具备相应的功能。这里显然 gsl 在我们的系统内是存在的，
而且默认找到的应该是我们装在系统目录下，而不是我们自己编译的版本。

对于缺少的包，有时是条件不具备，比如我们几乎没有什么可能在虚拟机中开展 GPU 计算，
所以这个 CUDA 也就没必要对接了。如果你是装在你的主机上，那么只要正确安装了 N
卡的驱动和对应的 CUDA 驱动，deal.II 将会自动对接。

有些包则是可以安装源里的，比如我们这里可以
\begin{verbatim}
sudo apt install libadolc-dev
\end{verbatim}
来对应
\begin{verbatim}
#      ( DEAL_II_WITH_ADOLC = OFF )
\end{verbatim}
来补上 \verb|ADOLC| 的支持。但是既然我们 deal.II 是手工编译的，那么显然对这些依赖包，
最佳的选择也是手工编译。一般而言，越是好的机器，越是前沿的计算工作，越需要系统性地手工编译。
而在学习阶段，或者在主要以桌面工作为主的系统上，可以直接采用源里的工具。
我自己的工作笔记本太重了没带来，是一台 HP 的移动工作站，插了 RTX 2080 的显卡。
作为一台仍然以桌面工作为主的笔记本，但已经具备一定的计算能力，我采取的策略是非直接计算的工具，
如 texlive，paraview，以及编译过于繁琐的工具，如系统内核，编译器，python，我都直接采用源里的，
但是和计算直接有关的包，比如 BLAS，CUDA，以及 deal.II 的全部依赖，我都是手工编译的。

而对于专业的计算服务器，则原则上应该从内核开始定制。这个时候，另一种 Linux 发行版 Gentoo
具有一定的管理优势。

不过凡事都不能极端，我们尽量发挥我们手中设备的效能，但关键是要不断前进。
不要让技术细节太多阻挡你前进。必要时适当的妥协以换取快速前进也没有什么不好的。

按照你的需求准备好这些包，不断地重试 CMake 以检测是否达到对接的要求。然后用
\verb|make| 和 \verb|make install| 就可以正确安装了。

而网站上的安装文档一定要仔细看，里面提供了各种 CMake 的参数设置。
\begin{verbatim}
https://www.dealii.org/9.4.1/readme.html
\end{verbatim}

我们接下去还是继续使用源内的 deal.II.

\section{作为 C++ 库的 deal.II}

和用 C 开发的 gsl 的基本模块是各种函数不一样，deal.II 是基于 C++ 开发的，
那就意味它的基础模块就是各种类，类是面向对象设计的一种概念，它认为一个功能模块，
都是数据结构和算法的统一体，因此一个类的特点是内部既包含各种成员函数以实现一定的功能，
也包含一定的属性以刻画对象的性质。我们看一个具体的类。

在
\begin{verbatim}
/usr/share/doc/libdeal.ii-doc/html$ 
\end{verbatim}
下有源安装 deal.II 的文档，我们可以用
\begin{verbatim}
firefox /usr/share/doc/libdeal.ii-doc/html/index.html
\end{verbatim}
打开。

我们进入我们熟悉的 step-1，找到第一个遇到的类 \verb|triangulation|，进入它的文档。

这里有一个很迷的点，\verb|triangulation| 从名字上看是三角剖分的意思，
但在 deal.II 中，实际上它不支持三角剖分。从拓扑的角度看，deal.II
支持的剖分本质上都是四边形或六面体这样的规则剖分。所以我们这里就把 \verb|triangulation|
看作剖分的意思。从文档看，\verb|triangulation| 有一个属性叫 \verb|dimension|，
显然是维数的意思，我们可以在 step-1 里面测试这个属性。全部的 tutorial 代码都可以在
\begin{verbatim}
/usr/share/doc/libdeal.ii-doc/examples
\end{verbatim}
找到，复制到我们的用户目录以便愉快地玩耍。

我们直接在 step-1 中将 grid-1 的 dimension 打印出来看看：
\begin{verbatim}
std::cout << "grid-1, dimension is: " 
          << triangulation.dimension << "." << std::endl;
\end{verbatim}

由于将一个类的属性，直接暴露在外部供不相干的对象访问，是不安全的，
所以一般所有的类属性都标志成私有的，无法访问。而需要读取数据的时候，
则通过一个只读函数来获取，比如这里的：\verb|n_levels()| 和 \verb|n_cells()|。
我们用这段代码来理解 grid-1 是如何生成的：
\begin{verbatim}
for (unsigned int i = 0; i < triangulation.n_levels(); i++)
    std::cout << "level " << i << " has "
              << triangulation.n_cells(i) << "." << std::endl;
\end{verbatim}

所以有了这本手册，我们理论上可以慢慢理解和使用 deal.II。

\section{怎么写这么一本手册？}

程序员最不想写的东西有两样：第一是注释，第二是使用文档。Doxygen 的出现将这二者合一，
从而使痛苦减少一半。

Doxygen是一个用于生成软件文档的工具，它可以从代码注释中自动生成项目的
详细文档。它支持多种编程语言，包括C++、Java、Python等，并且在许多开源
项目中被广泛使用。

使用Doxygen，开发者可以通过在代码中添加特殊格式的注释来描述类、函数、
变量和其他程序实体的用途、参数和返回值等信息。这些注释可以包含Markdown
风格的标记，使文档更易读和美观。

Doxygen可以生成多种输出格式的文档，包括HTML、LaTeX、RTF和XML等。它还支
持生成图形化的类关系图、调用图和文件依赖图等，帮助开发者更好地理解代码
结构和关系。

除了生成代码文档，Doxygen还可以生成函数调用关系、类继承关系和文件依赖
关系等图形化的统计信息，帮助开发者进行代码分析和优化。

总之，Doxygen是一个强大的工具，可以帮助开发者自动生成高质量的软件文档，
并提供代码分析和可视化的功能，使开发过程更加高效和方便。

首先我们要装一个 doxygen，这个 apt 可以搞定。然后我们需要写一个 C++ 的类...

\begin{verbatim}
/**
 * @file   Duck.h
 * @author Wang Heyu <hywang@ubuntu>
 * @date   Fri Jul  7 07:45:48 2023
 * 
 * @brief  This is a very stupid class.
 * 
 * 
 */

#include <iostream>

/**
 * Color for ducks!
 * 
 */
enum Color {
    RED,			/**< red color. */
    GREEN,			/**< greem color. */
    BLUE			/**< bule color. */
};

/**
 * Duck is a kind of powerful animal.
 * 
 */
class Duck
{
private:
    double weight;		/**< The weight of a duck. */
    Color color;		/**< The color of a duck. */
    int ID;			/**< Every duck has a ID no. */
public:
    /** 
     * Construct a duck.
     * 
     * @param _w to set the weight.
     * @param _c to set the color.
     * @param _I to set the ID.
     */
    Duck(double _w, Color _c, int _I) : weight(_w), color(_c), ID(_I) {};

    /** 
     * Long live the duck!
     * 
     */
    ~Duck() {
	std::cout << "Duck will never DIE!" << std::endl;
    };

    /** 
     * Even a duck will laugh...
     * 
     */
    void laugh () {
	std::cout << "Guaguagua!" << std::endl;
    };
};
\end{verbatim}

\bibliographystyle{plain}
\bibliography{crazyfish.bib}

\end{document}
